{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "3f644daa",
   "metadata": {},
   "source": [
    "## Week 1 - Exercise Solution  \n",
    "### AI Tutor: Dual-Model Explanation System  \n",
    "\n",
    "Welcome to your **AI Tutor**!  \n",
    "Here, you’ll get explanations from **two teachers - Llama and GPT**.  \n",
    "Both receive the **same question**, and each provides a unique explanation.  \n",
    "You can **compare their answers** and decide **which one explains better**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "628ca840",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Imports\n",
    "import os\n",
    "from openai import OpenAI\n",
    "from dotenv import load_dotenv\n",
    "from IPython.display import display, Markdown, update_display"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac263d00",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Models\n",
    "MODEL_GPT = 'gpt-4o-mini'\n",
    "MODEL_LLAMA = 'llama3.2'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7d12d906",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load environment variables in a file called .env\n",
    "\n",
    "load_dotenv(override=True)\n",
    "api_key = os.getenv('OPENAI_API_KEY')\n",
    "\n",
    "# Check the key\n",
    "\n",
    "if not api_key:\n",
    "    print(\"No API key was found - please head over to the troubleshooting notebook in this folder to identify & fix!\")\n",
    "elif not api_key.startswith(\"sk-proj-\"):\n",
    "    print(\"An API key was found, but it doesn't start sk-proj-; please check you're using the right key - see troubleshooting notebook\")\n",
    "elif api_key.strip() != api_key:\n",
    "    print(\"An API key was found, but it looks like it might have space or tab characters at the start or end - please remove them - see troubleshooting notebook\")\n",
    "else:\n",
    "    print(\"API key found and looks good so far!\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f4b42f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "system_prompt = \"\"\"\n",
    "You are **Expert Explainer**, an advanced reasoning assistant whose role is to **explain any question the user provides** — whether it’s about code, a technical concept, or general knowledge — in a **clear, structured, and step-by-step** manner.\n",
    "\n",
    "### Your goal\n",
    "Help the user **fully understand** the question by breaking it down logically, defining terms, and explaining each part or step clearly.\n",
    "\n",
    "### Explanation Guidelines\n",
    "1. **Detect the question type**  \n",
    "   - If it’s *procedural* (like code, math, or reasoning): explain **step-by-step**.  \n",
    "   - If it’s *conceptual* or *descriptive*: explain **by clear, logical parts**.  \n",
    "\n",
    "2. **Output structure (always):**\n",
    "   1. **Summary:** Short 2–3 line overview of the topic.  \n",
    "   2. **Plan:** Bullet outline of what will be explained.  \n",
    "   3. **Main Explanation:**  \n",
    "      - Divide into **steps** or **parts** with short headings.  \n",
    "      - Define key terms before using them.  \n",
    "      - Provide small, relevant examples or analogies.  \n",
    "   4. **Common Pitfalls or Misunderstandings:** Short list of things people often get wrong.  \n",
    "   5. **Quick Recap:** One paragraph summary of the main idea.  \n",
    "\n",
    "3. **Tone & Style**\n",
    "   - Be **clear, logical, and didactic** — like a skilled teacher.  \n",
    "   - Avoid unnecessary jargon unless defined.  \n",
    "   - Use bullet points and short sections when possible.  \n",
    "   - If context is missing, state assumptions before explaining.  \n",
    "\n",
    "4. **For code or math questions**\n",
    "   - Use small runnable or line-by-line examples.  \n",
    "   - Add short comments to clarify what happens in each step.  \n",
    "   - When formulas appear, define every variable and show the reasoning process.  \n",
    "\n",
    "5. **Integrity**\n",
    "   - Never guess. If information is missing, note what’s needed to complete the explanation.  \n",
    "   - Prefer correctness and clarity over brevity.  \n",
    "\n",
    "### User input\n",
    "The user will provide **only a single question or topic** (e.g. “How does recursion work?” or “Explain this Python snippet”).  \n",
    "You must **automatically detect** the best explanation style (step-by-step or by parts) and deliver a clear, structured explanation following the rules above.\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "# Example\n",
    "user_prompt = \"\"\"\n",
    "Explain this Python code step by step:\n",
    "\n",
    "def factorial(n):\n",
    "    if n == 0:\n",
    "        return 1\n",
    "    else:\n",
    "        return n * factorial(n - 1)\n",
    "\n",
    "print(factorial(5))\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ffca306b",
   "metadata": {},
   "outputs": [],
   "source": [
    "messages = [\n",
    "    {\"role\": \"system\", \"content\": system_prompt},\n",
    "    {\"role\": \"user\", \"content\": user_prompt}\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7eabbe83",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create GPT object\n",
    "gpt = OpenAI()\n",
    "\n",
    "stream_gpt = gpt.chat.completions.create(\n",
    "    model = MODEL_GPT,\n",
    "    messages = messages,\n",
    "    stream = True,\n",
    ")\n",
    "\n",
    "OLLAMA_BASE_URL = \"http://localhost:11434/v1\"\n",
    "\n",
    "# Create Ollama object\n",
    "llama = OpenAI(base_url=OLLAMA_BASE_URL, api_key=\"ollama\")\n",
    "\n",
    "stream_llama = llama.chat.completions.create(\n",
    "    model = MODEL_LLAMA,\n",
    "    messages = messages,\n",
    "    stream = True,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dcf72063",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Display Ollama response\n",
    "print(f\"LLAMA explanation:\\n\")\n",
    "\n",
    "response_llama = \"\"\n",
    "display_handle = display(Markdown(\"\"), display_id=True)\n",
    "for chunk in stream_llama:\n",
    "    response_llama += chunk.choices[0].delta.content or ''\n",
    "    update_display(Markdown(response_llama), display_id=display_handle.display_id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d751801",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Display GPT response\n",
    "print(f\"GPT explanation:\\n\")\n",
    "\n",
    "response_gpt = \"\"\n",
    "display_handle = display(Markdown(\"\"), display_id=True)\n",
    "for chunk in stream_gpt:\n",
    "    response_gpt += chunk.choices[0].delta.content or ''\n",
    "    update_display(Markdown(response_gpt), display_id=display_handle.display_id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dbcb4318",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
